#ifndef _PHYSICS_
#define _PHYSICS_

// If you want to override gravity, don't do here.
// Do it before include this file ( or entity file ).
// if you do #define GRAVITY 5.0f you're overriding correctly.

#ifndef GRAVITY
	const float32 GRAVITY = 10.0f;
#endif

// If you want to use your own world pointer (I do not know why) you can do:
// #define _WORLD_ MY_WORLD
// b2World * MY_WORLD;
// where MY_WORLD is the name of variable.

#ifndef _WORLD_
	b2World * world = 0;
#else
	b2World * world = _WORLD_ 
#endif

// Some miscelaneus functions (like file management).
#include "misc.h"

// Collision ball group. Used because players can't
// jump from one side to the other, but ball can.
#define COLLISION_BALL 1

// Predefined needed function.
void InitPhysicsWorld(bool = false);
// PhysicsObject is main collision detect and physics movement class.
// If you want an object without need to draw, use this instead
// entity classes.
class PhysicsObject
{
private:
	b2Shape *_shape;
	b2ShapeDef _lastshp;
	b2Body * Body;
public:
	// These variables MUST be defined BEFORE
	// SetBox or SetCircle.
	float32 density;
	float32 restitution;
	float32 friction;
	b2FilterData filter;
	// fixedRotation means no automatic rotation for object (like players).
	// You can rotate it manually.
	PhysicsObject(bool fixedRotation = false)
	{

		InitPhysicsWorld();
		b2BodyDef bd;
		bd.fixedRotation = fixedRotation;
		Body = world->CreateBody(&bd);
		density = 0;
		restitution = 0.05f;
		friction = 1;
		_shape = 0;
		filter.categoryBits = 1;
		filter.maskBits = 0xFFFF;
		filter.groupIndex = 0;
	}
	// Set your own shape to object.
	void MakeShape(b2ShapeDef * shpdef)
	{
		if (_shape != 0)
			Body->DestroyShape(_shape);
		_shape = Body->CreateShape(shpdef);
		Body->SetMassFromShapes(); 
		_lastshp = shpdef[0];
	}
	// SetBox or SetCircle are functions that
	// make predefined shapes.
	void SetBox(b2AABB box)
	{

		static b2PolygonDef shpdef;
		shpdef.density = density;
		shpdef.restitution = restitution;
		shpdef.friction = friction;
		shpdef.SetAsBox(PHYSICS_SCALE*(box.lowerBound.x - box.upperBound.x)/2,PHYSICS_SCALE*(box.lowerBound.y - box.upperBound.y)/2);
		shpdef.filter = filter;
		MakeShape(&shpdef);
	}
	void SetBox(float32 x, float32 y)
	{
		b2AABB box;
		box.lowerBound = b2Vec2(x,y);
		box.upperBound = b2Vec2_zero;
		SetBox(box);
	}
	void SetCircle(float32 rad)
	{
		static b2CircleDef shpdef;
		shpdef.density = density;
		shpdef.restitution = restitution;
		shpdef.friction = friction;
		shpdef.radius = PHYSICS_SCALE*rad;
		shpdef.filter = filter;
		MakeShape(&shpdef);
	}
	// SetVertices make a shape where you Vertex are.
	// Count is how much Vertex there are.
	void SetVertices(b2Vec2 * Vertex, int Count)
	{
		if (Count < 3)
			return;
		if (_shape != 0)
			Body->DestroyShape(_shape);
		b2PolygonDef shpdef;
		shpdef.vertexCount = Count;
		for (int i = 0;i<Count;i++)
		{
			shpdef.vertices[i] = Vertex[i];
			shpdef.vertices[i] *= PHYSICS_SCALE;
		}
		shpdef.density = density;
		shpdef.restitution = restitution;
		shpdef.friction = friction;
		shpdef.filter = filter;
		MakeShape(&shpdef);
		
	}
	// Getters and Setters of angle y position.
	// Angles are in Sexagesimal.
	// Position is from center.
	void SetAngle(float32 newangle)
	{
		b2Vec2 pos = GetPosition();
		pos *= PHYSICS_SCALE;
		Body->SetXForm(pos,SexagesimalToRadian(newangle));
	}
	float32 GetAngle()
	{
		return RadianToSexagesimal(Body->GetAngle());		
	}
	void SetPosition(b2Vec2 newpos)
	{
		newpos *= PHYSICS_SCALE;
		Body->SetXForm(newpos,GetAngle());
		WakeUp();
	}
	b2Vec2 GetPosition()
	{
		b2Vec2 result = Body->GetPosition();
		result *= 1/PHYSICS_SCALE;
		return result;
	}
	// Load physics from a file:

	// s or c (square or circle)
	// x,y (if s), radius (if circle).
	// density
	// friction
	// restitution
	// group filtering

	bool LoadPhysics(char * path)
	{
		FILE * f = 0;
		f = fopen(path,"r");
		if (f == 0)
		{
			char Data[MAX_PATH];
			Data[0] = '\0';
			strcat(Data,hge->Resource_MakePath());
			strcat(Data,path);
			f = fopen(Data,"r");
			if (f == 0)
				return false;
		}
		b2Vec2 * Vertex;
		char str[30];
		char c;
		strcpy(str,GetLineIgnoreComments(f,0));
		if(strcmp(str,"s") == 0)
		{
			Vertex = new b2Vec2;
			Vertex[0] = b2Vec2_zero;
			// Ignore comments:
			__int64 n = GetFileRealPosition(f);			
			while(GetFileLine(f)[0] == '#')
				n = GetFileRealPosition(f);
			SetFileRealPosition(f,n);
			n = 0;
			for(c = fgetc(f); c != '\n'; c = fgetc(f))
			{
				if(c == ',')
				{
					str[n] = '\0';
					Vertex->x = FLOAT(atof(str));
					str[0] = '\0';
					n = 0;
					continue;
				}
				str[n] = c;
				n++;
			}
			SetFileRealPosition(f,GetFileRealPosition(f));
			str[n] = '\0';
			Vertex->y = FLOAT(atof(str));
			str[0] = '\0';
			density = FLOAT(atof(GetLineIgnoreComments(f,GetFileRealPosition(f))));
			friction = FLOAT(atof(GetLineIgnoreComments(f,GetFileRealPosition(f))));
			restitution = FLOAT(atof(GetLineIgnoreComments(f,GetFileRealPosition(f))));
			filter.groupIndex = INT(atof(GetLineIgnoreComments(f,GetFileRealPosition(f))));
			SetBox(Vertex->x,Vertex->y);
			return true;
		}
		if(strcmp(str,"c") == 0)
		{
			float32 radius = FLOAT(atof(GetLineIgnoreComments(f,GetFileRealPosition(f))));
			density = FLOAT(atof(GetLineIgnoreComments(f,GetFileRealPosition(f))));
			friction = FLOAT(atof(GetLineIgnoreComments(f,GetFileRealPosition(f))));
			restitution = FLOAT(atof(GetLineIgnoreComments(f,GetFileRealPosition(f))));
			filter.groupIndex = INT(atof(GetLineIgnoreComments(f,GetFileRealPosition(f))));
			SetCircle(radius);
			return true;
		}
		fclose(f);
		return false;
	}
	// Get the last Shape Definition.
	b2ShapeDef * GetLastShapeDef()
	{
		return &_lastshp;
	}
	// Returns box2d Body
	b2Body * GetBody()
	{
		return Body;
	}
	// Physics functions:
	void SetAngularV(float32 newv)
	{
		GetBody()->SetAngularVelocity(newv);
	}
	float32 GetAngularV()
	{
		return GetBody()->GetAngularVelocity();
	}
	void SetLinearV(b2Vec2 newv)
	{
		GetBody()->SetLinearVelocity(newv);
	}
	b2Vec2 GetLinearV()
	{
		return GetBody()->GetLinearVelocity();
	}
	bool IsSleeping()
	{
		return GetBody()->IsSleeping();
	}
	void WakeUp()
	{
		if (IsSleeping())
			GetBody()->WakeUp();
	}
	b2Vec2 GetWCenter()
	{
		return GetBody()->GetWorldCenter();
	}
	void ApplyForceCenter(b2Vec2 imp)
	{
		GetBody()->ApplyForce(imp,GetWCenter());
	}
	void RemoveGravity()
	{
		ApplyForceCenter(b2Vec2(0,GRAVITY*Body->GetMass()));
	}
	void ApplyAceleration(b2Vec2 ac)
	{
		WakeUp();
		SetLinearV(GetLinearV() + ac);
	}
	void Freeze()
	{
		GetBody()->SetLinearVelocity(b2Vec2_zero);
		GetBody()->SetAngularVelocity(0);
	}
};

// Some invisible objects.
PhysicsObject ground[4];
PhysicsObject playerlimits;

// This function have 2 things to do. First init world,
// (when InitGrounds = false), called by the first PhysicsObject.
// The second is called once when you want to initate the game,
// it makes ground, walls and celling (InitGrounds = true).
void InitPhysicsWorld(bool InitGrounds)
{
	if (!InitGrounds)
	{
		// Stop if it is already initiated.
		if (world != 0)
			return;
		b2AABB worldAABB;
		worldAABB.lowerBound.Set(-10000000, -10000000);
		worldAABB.upperBound.Set(10000000,10000000);
		b2Vec2 gravity(0.0f, -10.0f);
		world = new b2World(worldAABB, gravity, true);
	}
	else
	{

		ground[1].friction = ground[2].friction = ground[3].friction = playerlimits.friction = 0;
		ground[0].filter.groupIndex = ground[1].filter.groupIndex = ground[2].filter.groupIndex = ground[3].filter.groupIndex = 2;
		ground[0].SetBox(FLOAT(GAME_WIDTH),50); ground[1].SetBox(FLOAT(GAME_WIDTH),50);
		ground[2].SetBox(50 ,FLOAT(GAME_HEIGHT)); ground[3].SetBox(50 ,FLOAT(GAME_HEIGHT));

		ground[0].SetPosition(b2Vec2(GAME_WIDTH/2,-25));
		ground[1].SetPosition(b2Vec2(GAME_WIDTH/2,GAME_HEIGHT + 25));
		ground[2].SetPosition(b2Vec2(-25,GAME_HEIGHT / 2));
		ground[3].SetPosition(b2Vec2(GAME_WIDTH + 25,GAME_HEIGHT / 2));

		playerlimits.filter.groupIndex = -COLLISION_BALL;
		playerlimits.SetBox(20,GAME_HEIGHT*2);
		playerlimits.SetPosition(b2Vec2(GAME_WIDTH/2,GAME_HEIGHT/2));
		
	}
}
#endif
